﻿#region << 版 本 注 释 >>
/*----------------------------------------------------------------
 * 版权所有 (c) 2022 北京超维景生物科技有限公司 保留所有权利。
 * 
 * 创建者：huangyang
 * 电子邮箱：huangyang@tvscope.cn
 * 创建时间：2023/2/21 17:45:12
 * 版本：V1.0.0
 * 描述：
 *
 * ----------------------------------------------------------------
 * 修改人：
 * 时间：
 * 修改说明：
 *
 * 版本：V1.0.1
 *----------------------------------------------------------------*/
#endregion << 版 本 注 释 >>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ImageK.Process
{
    /** A table for easier downsizing by convolution with a kernel.
     *	Supports the interpolation methods of ImageProcessor: none, bilinear, bicubic
     *	Convention used: The left edges of the first pixel are the same for source and destination.
     *	E.g. when downsizing by a factor of 2, pixel 0 of the destination
     *	takes the space of pixels 0 and 1 of the source.
     *
     *	Example for use: Downsizing row 0 of 'pixels' from 'roi.width' to 'destinationWidth'.
     *	The input range is given by the roi rectangle.
     *	Output is written to row 0 of 'pixels2' (width: 'destinationWidth')
     <code>
	    DownSizeTable dt = new DownSizeTable(width, roi.x, roi.width, destinationWidth, ImageProcessor.BICUBIC);
	    int tablePointer = 0;
	    for (int srcPoint=dt.srcStart, srcPoint<=dt.srcEnd; srcPoint++) {
		    float v = pixels[srcPoint];
		    for (int i=0; i<dt.kernelSize; i++, tablePointer++)
			    pixels2[dt.indices[tablePointer]] += v * dt.weights[tablePointer];
     </code>
     */
	public class DownsizeTable
    {
		/** Number of kernel points per source data point */
		public readonly int kernelSize;
		/** index of the first point of the source data that should be accessed */
		public readonly int srcStart;
		/** index of the last point of the source data that should be accessed */
		public readonly int srcEnd;
		/** For each source point between srcStart and srcEnd, indices of destination
		 *	points where the data should be added.
		 *	Arranged in blocks of 'kernelSize' points. E.g. for kernelSize=2, array
		 *	elements 0,1 are for point srcStart, 2,3 for point srcStart+1, etc. */
		public readonly int[] indices;
		/** For each source point, weights for adding it to the destination point
		 *	given in the corresponding element of 'indices' */
		public readonly float[] weights;
		/** Kernel sizes corresponding to the interpolation methods NONE, BILINEAR, BICUBIC */
		private readonly static int[] kernelSizes = new int[] { 1, 2, 4 };
		private readonly int srcOrigin, srcLength;
		private readonly double scale;         //source/destination pixel numbers
		private readonly int interpolationMethod;
		private readonly static int UNUSED = -1; //marks unused entries in 'indices' array


		/** Create a table for 1-dimensional downscaling interpolation.
		 *	Interpolation is done by 
		 * @param srcSize	 Size of source data, i.e., width or height of input image
		 * @param srcOrigin	 Index of first pixel of source data that corresponds to an ouput pixel,
		 *					 0 or origin of source rectangle if only a roi is scaled
		 * @param srcLength	 Number of pixels of source data that should correspond to output, i.e.,
		 *					 width or height of source roi
		 * @param dstSize	 Number of destination pixels.
		 * @param interpolationMethod One of the methods defined in ImageProcessor: NONE, BILINEAR, BICUBIC
		 */
		public DownsizeTable(int srcSize, int srcOrigin, int srcLength, int dstSize, int interpolationMethod)
		{
			this.srcOrigin = srcOrigin;
			this.srcLength = srcLength;
			this.interpolationMethod = interpolationMethod;
			this.scale = srcLength / (double)dstSize;
			this.kernelSize = kernelSizes[interpolationMethod];
			int srcStartUncorr = (int)(Math.Ceiling(1e-8 + srcIndex(-0.5 * kernelSize))); //may be <0
			srcStart = srcStartUncorr < 0 ? 0 : srcStartUncorr;    //corrected value, avoids pointing out of array
			int srcEndUncorr = (int)(Math.Floor(1e-8 + srcIndex(dstSize - 1 + 0.5 * kernelSize)));
			srcEnd = srcEndUncorr >= srcSize ? srcSize - 1 : srcEndUncorr;
			int arraySize = (srcEnd - srcStart + 1) * kernelSize;
			indices = new int[arraySize];
			weights = new float[arraySize];
			Array.Fill(indices, UNUSED);
			//IJ.log("src size="+srcSize+" range="+srcStart+"-"+srcEnd+" array:"+arraySize+" scale="+(float)scale);

			for (int dst = 0; dst < dstSize; dst++)
			{
				double sum = 0;
				int lowestS = (int)(Math.Ceiling(1e-8 + srcIndex(dst - 0.5 * kernelSize)));
				int highestS = (int)(Math.Floor(-1e-8 + srcIndex(dst + 0.5 * kernelSize)));
				for (int src = lowestS; src <= highestS; src++)
				{
					//out of bounds policy is 'use value of edge pixel'.
					//We therfore replace an out-of-bounds pixel by the edge pixel:
					int s = src < 0 ? 0 : (src >= srcSize ? srcSize - 1 : src);
					int p = (s - srcStart) * kernelSize;// points to first value in 'indices' and 'weights'
														// arrays reserved for this source pixel
					while (indices[p] != UNUSED && indices[p] != dst)
						p++;                    //position used for other destination pixel, try the next one
												//if(p-(s-srcStart)*kernelSize>=kernelSize)IJ.log(srcSize+">"+dstSize+": too long: src="+src+" dst="+dst);
					indices[p] = dst;
					float weight = kernel(dst - dstIndex(src));
					sum += weight;
					weights[p] += weight;
					//IJ.log("src="+src+"("+s+") to "+dst+" w="+weight+" p="+p);
				}
				//normalize: sum of weights contributing to this destination pixel should be 1
				int iStart = (lowestS - srcStart) * kernelSize;
				if (iStart < 0) iStart = 0;
				int iStop = (highestS - srcStart) * kernelSize + (kernelSize - 1);
				if (iStop >= indices.Length) iStop = indices.Length - 1;
				//IJ.log("normalize "+iStart+"-"+iStop+" sum="+sum);
				for (int i = iStart; i <= iStop; i++)
					if (indices[i] == dst)
						weights[i] = (float)(weights[i] / sum);
			}
			for (int i = 0; i < indices.Length; i++)
				if (indices[i] == UNUSED)
					indices[i] = 0;     //set unused entries to pixel 0 (weight is 0 anyhow), then they do no harm
		}

		// Converts a destination pixel coordinate (index) to the corresponding
		// source coordinate
		// All coordinates refer to the centers of the pixel
		// Also for fractional indices, e.g. dstIndex = i-0.5 for left edge of pixel.
		// The output is also fractional (not converted to int)
		// No check for source array bounds, may result in a value <0 or >= array size.
		private double srcIndex(double dstIndex)
		{
			return srcOrigin - 0.5 + (dstIndex + 0.5) * scale;
		}
		// Converts the coordinate (index) of a source pixel to the destination pixel
		private double dstIndex(int srcIndex)
		{
			return (srcIndex - srcOrigin + 0.5) / scale - 0.5;
		}
		// Calculates the kernel value. Only valid within +/- 0.5*kernelSize
		protected float kernel(double x)
		{
			switch (interpolationMethod)
			{
				case ImageProcessor.NONE:
					return 1f;
				case ImageProcessor.BILINEAR:
					return 1f - (float)Math.Abs(x);
				case ImageProcessor.BICUBIC:
					return (float)ImageProcessor.cubic(x);
			}
			return float.NaN;
		}
	}
}
